#include "udpprotocol.h"

// Adress
struct sockaddr_in raddr;
struct hostent *hostinfo;

// Puffer
SharedMem *mem;

// SharedMemory und semaphore fileDescriptor
int shmid; 
int sphid;

/**
* Send a Item ( paket)
* @param pro Paket to send
*/
void sendProtocol(char *pro)
{

	char toSend[100];
	sprintf(toSend, pro);
	
	// versuchen zu schicken
	if(0 > sendto(sockN, toSend, strlen(toSend), 0, 
	(struct sockaddr* )&raddr, 
	sizeof(raddr)))
	{
		perror("Problem with send()");
		exit(1);
	}
	// Paket in Shared Memory(in puffer fuer gesendeten Daten)  kopieren
	strcpy(&(mem->ouPuffer[mem->ouWdx][0]),toSend);
	mem->ouWdx = (mem->ouWdx+1)%BUFFER_SIZE;
	printf("%s\n", pro);	// Console Ausgabe
}

int getrep()
{
	down(sphid, 0);
	int rep = mem->reject;
	up(sphid, 0);
	return rep;
}

/**
* Send a random message
* The number of Packet is random define
*/
void sendMessage()
{
	// dataMax ist der Anzahl von Paketten des 
	//Nachricht zu schicken
	//er ist zuffaelig initialisiert
	int dataMax = random()%15; 
	char toSend[100];
	int type = 0;
	 
	while(dataMax > 0)
	{
		sprintf(toSend, "CON_DATA%d", dataMax);
		// Semaphore zaehler dekrementieren :
		// wird inkrementiert wenn ein Antwort bekommen wird
		down(sphid, 1);
		// kritische Abschnitt : zugriff auf der SharedMemory
		down(sphid, 0);
		type = mem->type;
		if(!mem->type) // No type define: d.h stop 
		   {
			up(sphid, 0);
			break;
		   }
		// Paket schicken
		sendProtocol(toSend);
		// kritischen region verlassen
		up(sphid, 0);
		// bisschen warten
		sleep(random()%5);
		dataMax--;
	}
	// Wenn alle Packet geschickt wuerde, dann CON_CLOSE schicken 
	if(dataMax == 0)
	{
		down(sphid, 0);
		sendProtocol(CON_CLOSE);
		up(sphid, 0);
	}
	// Warten auf alle Antworten
	down(sphid, 2);
	
}


/**
* Send Reponse to Server
* Warte auf Dattenpakete und sende entsprechende Antwort
*/
void sendReponse()
{
	
	char request[100];
	while(1){
		int antw = 0;
		// Warten solange keine Nachrichten da ist 
		do{
			down(sphid, 0);
			antw = mem->reponses;
			up(sphid, 0);
		}
		while(antw <= 0);
		
		int stop = 0; 
		down(sphid, 0); // kritische Abschnitt : SharedMemory zugriff
		mem->reponses --;
		// Paket zu antworten auslesen
		memset(request, 0, 100);
		strcpy(request, &(mem->inPuffer[mem->inRdx][0]));
		mem->inRdx = (mem->inRdx+1)%BUFFER_SIZE;
		stop = mem->stop;
		up(sphid, 0);
	
		// Aufhoeren wenn aufhoeren musst
		// d.h wenn request = conclose || conreject
		// oder wenn der flag stop ist gesetzt
		if(stop== STOP||!strcmp(request, CON_CLOSE) || !strcmp(request, CON_REJECT))
		{
			down(sphid, 0);
			if(stop!=STOP)
				sendProtocol(CON_CLOSE_ACK);
			printf("\n*******************************\n");
    			initSharedMemory(mem, sizeof(SharedMem));
			up(sphid, 0);

			break;
		}
		/// Sonst die entsprechende Nachrichten schicken
		else if(!strcmp(request, CON_REQ))
		{	
			down(sphid, 0);
			sendProtocol(CON_ACK);
			up(sphid, 0);
		}
		else 
		{	down(sphid, 0);
			sendProtocol(CON_DATA_ACK);
			up(sphid, 0);	
		}
		sleep(random()%5);
	}
	// Kind Prozess kann weiter
	up(sphid, 3);
}

/**
* Receive a Item
*
* Ein Paket bekommen
* Warten maximal 30 s
*/
void receiveItem(char* item, int size)
{
	// Laenge des Adress des Host
	int length = sizeof(raddr);
	// item "leeren"
	memset(item, 0, size);
	struct timeval	timeo;
	timeo.tv_sec  = 30; // Warten maximal 30 sekunde
	timeo.tv_usec = 0;

	// timeout fuer das Socket initialisieren
	if(0 > setsockopt ( sockN,
                   SOL_SOCKET,
                   SO_RCVTIMEO,
                   &timeo, sizeof (timeo)))
	{
		perror("Problem setsockopt()");
		exit(1);
	}

	if(0 > recvfrom(sockN, item, size, 0, 
			(struct sockaddr *)&raddr, &length))
	{
		if(errno == EAGAIN) // nicht stoppen
			sprintf(item, "NO DATA"); // Keine Daten ist empfangen worden 
		else{
			perror("Problem recvfrom()");
			exit(1);
		}
	}
	// Empfangende Nachrichten in SharedMemory kopieren
	down(sphid, 0); // ist natuerlich eine kritische Abschnitt ...
	strcpy(&(mem->inPuffer[mem->inWdx][0]),	item);
	mem->reponses ++;
	printf("\t\t%s \n",&(mem->inPuffer[mem->inWdx][0]));
	mem->inWdx = (mem->inWdx+1)%BUFFER_SIZE;
	up(sphid, 0);
}

/**
* Receive a Message ( Many Item)
*/
void receiveMessage()
{
	char item[4000]; // bekommte Paket
	char mess[4000]; // vorherige bekomnte Paket 
			 // 
	while(1)
	{
		memset(mess, 0, 4000);
		down(sphid, 0);
		int indp = mem->inWdx -1 ;
		indp = indp <0 ? BUFFER_SIZE -1 : indp;
		strcpy(mess, &(mem->inPuffer[indp][0]));
		up(sphid, 0);
		receiveItem(item, 4000);
		// Check auf Konsistence zwischen mess und item
		if(
		 (!strcmp(mess, CON_REQ) && !strncmp(item, "CON_DATA", strlen("CON_DATA")) )||
		 (!strncmp(mess, "CON_DATA", strlen("CON_DATA")) &&  !strncmp(item, "CON_DATA", strlen("CON_DATA")) ) ||
		 (!strcmp(item, CON_REQ) && strlen(mess)== 0) )
			continue;
		else
		{
			// wenn item der letzte Nachricht war oder es besteht keine 
			// Konsistence, dann musst stoppen
			if(strcmp(item, CON_CLOSE) && strcmp(item, CON_REJECT))
			{
				down(sphid, 0);
				mem->stop = STOP;
				up(sphid, 0);
			}
			break;
		}
	}
	// Warten auf Vater  Prozess
	down(sphid, 3);
	
	
}

/**
* Receive a Reponse (Many paket)
*/
void receiveReponse()
{
	char item[4000]; //bekommte Antwort
	char mess[4000]; //vorherige bekommte Antwort
	while(1)
	{
		receiveItem(item, 4000); // Antwort bekommen
		up(sphid, 1);		 // Semaphore inkrementieren ( Anzahl freie Platz geht hoch)
		memset(mess, 0, 4000);
		down(sphid, 0);
		int ind = mem->inWdx;
		ind = ind -1 >= 0?ind -1: BUFFER_SIZE-1;
		strcpy(mess, &(mem->inPuffer[ind][0]));
		up(sphid, 0);
		// Prueft auf Konsistenz
		if(
			(!strcmp(item, CON_CLOSE_ACK)||!strcmp(item, CON_REJECT)) ||
			(!strcmp(item, CON_ACK) && strcmp(mess, CON_REQ)) ||
			(!strcmp(item, CON_DATA_ACK) && strncmp(mess, "CON_DATA", strlen("CON_DATA")))||
			(!strcmp(item, CON_CLOSE_ACK) && strcmp(mess, CON_CLOSE))||
			(!strcmp(item, CON_REQ) || !strcmp(item, CON_CLOSE)))
		{
			// ende, wenn ende oder wenn keine Konsistenz
			printf("\n*******************************\n");
			break;
		}

		
	}
	// Alle wieder initialisieren
	down(sphid, 0);
    	initSharedMemory(mem, sizeof(SharedMem));
	up(sphid, 0);
	// Semaphore hoch : Vater Prozess kann weiter
	up(sphid, 2);
}

/** Main Methode **/
int main(int argc, char** argv)
{

	short port;
	short rport;
	
	if(argc != 4)
	{
		printf("udp port rhostname rport\n");
		exit(1);
	}
	
	
	port = atoi(argv[1]);
	rport = atoi(argv[3]);
	
	if(0 > (hostinfo = gethostbyname(argv[2])))
	{
		perror("Problem with gethostbyname()");
		exit(1);
	}
	// Signal Handler initialisieren
	signal(SIGTERM, signalhandler);
	signal(SIGINT, signalhandler);
	//Socket erzeugen
    	sockN = getSocket(port);
	//sharedMemory
	shmid = getSharedMemory(SHM_KEY, (IPC_CREAT|SHM_PERM), sizeof(SharedMem));
	//semahphore
    	sphid = getSemaphor(SEM_KEY, SEM_NUM, (IPC_CREAT|SEM_PERM));
	initSemaphor(sphid, 0, 1);	// Zugriff au shareMemory
	initSemaphor(sphid, 1, BUFFER_SIZE-1); //Zuchicken  narichten
	initSemaphor(sphid, 2, 0);
	initSemaphor(sphid, 3, 0);
	
	memset((void *)&raddr, 0, sizeof(raddr));
	raddr.sin_family = AF_INET;
	raddr.sin_port = htons(rport);
	memcpy(&raddr.sin_addr.s_addr, hostinfo->h_addr, hostinfo->h_length); 
	
	if ( 0 > ( mem = shmat(shmid, 0, 0) ) ) {
		perror(" problem with shmat()");
		exit(1);
	}
	printf("--- %d ----\n", getpid());
     	initSharedMemory(mem, sizeof(SharedMem));
     	int pid = fork();

	/** SEND **/
    	if(pid)
    	{
		char toSend[100];
		srandom(time(NULL) + getpid());
		while(1){
			sleep(random()%5);
			down(sphid, 0);
			// fuehre generell zu chaos ?*+#+!
			if(mem->reject == REJECT_ACK)
			{
				initSharedMemory(mem, sizeof(SharedMem));
				up(sphid, 2);
				up(sphid, 0);
				continue;
			}
			// ich bin receiver, ich schicke Antworten
			else if(mem->type == RECEIVER)
			{
				up(sphid, 0);
				sendReponse();
			}
			
			else
			{
				mem->type = SENDER;
				sendProtocol(CON_REQ);
				up(sphid, 0);
				while(!getrep()); // Warte auf erste Antwort
				
				/// ist Angenommen worden ? ///
				// keine Verbindung moeglich, ich schicke "nein" zurueck
				if(getrep() == REJECT_REQ)
				{
					down(sphid, 0);
					sendProtocol(CON_REJECT);
					printf("\n*******************************\n");
					initSharedMemory(mem, sizeof(SharedMem));
					up(sphid, 0);
					up(sphid, 3);
					continue; 
				}
				// keine Verbindung moeglich, ich habe "nein" bekommen
				else if(getrep() == REJECT_ACK)
				{
					down(sphid, 0);
					initSharedMemory(mem, sizeof(SharedMem));
					up(sphid, 0);
					up(sphid, 2);
					continue;
				}
				// Verbindung is gebaut, ich kann der Nachricht schicken	
				sendMessage();
				
			}
			
		}
    	}

	/**RECEIVE**/
    	else
    	{
		char item[4000];
		while(1)
		{
			// Erste Paket : sollt CON_ACK or CON_REQ 
			receiveItem(item, 4000);
			down(sphid, 0);
			// Besonder Fehler
			if(!strcmp(item, CON_REQ) && mem->type == SENDER)
			{
				mem->reject = REJECT_REQ;
				up(sphid, 0);
				down(sphid, 3);
			}
	
			// Bekommt Naricht 
			else if(!strcmp(item, CON_REQ))
			{
				mem->type = RECEIVER;
				up(sphid, 0);
				receiveMessage();	
			}
			// bekommt Antworten
			else if(!strcmp(item, CON_ACK))
			{
				mem->reject = ACCEPT;
				up(sphid, 0);
				receiveReponse();
			}
			//Fehler
			else //if(!strcmp(item, CON_REJECT))
			{	
				mem->reject = REJECT_ACK;
				printf("\n*******************************\n");
				up(sphid, 0);
				down(sphid, 2);
			}
	
		}
    	}
	return 0;
}
